package com.akaovi.shareWare.server;

import com.akaovi.shareWare.mess.*;
import com.akaovi.shareWare.utils.LRUCache;
import com.akaovi.shareWare.gui.ProcessBar;
import com.akaovi.shareWare.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author akaovi
 * @version 1.0
 * @description 服务端 （接收, 推送拉取）
 * @date 2022/10/13 23:23
 * @since 2022/10/13 23:23
 */
public class Server implements ServerFunc {

    private static final Logger logger = LoggerFactory.getLogger(Server.class);

    private static boolean isLoop = true;

    private boolean refuseAllFile = false;

    private final ThreadPoolExecutor threadPool;

    private final LRUCache<String> lruCache = new LRUCache<>(Utils.getMaxCacheSize());

    public boolean isLoop() {
        return isLoop;
    }

    public boolean isRefuseAllFile() {
        return refuseAllFile;
    }

    public ThreadPoolExecutor getThreadPool() {
        return threadPool;
    }

    public LRUCache<String> getLruCache() {
        return lruCache;
    }

    private void setLoop(boolean loop) {
        isLoop = loop;
    }

    private void setRefuseAllFile(boolean refuseAllFile) {
        this.refuseAllFile = refuseAllFile;
    }

    public Server(ThreadPoolExecutor threadPool) {
        this.threadPool = threadPool;
    }

    /**
     * 保存来自客户端文件
     *
     * @param mess
     * @param socket
     * @return
     */
    private boolean acceptFile(FileMess mess, Socket socket) {
        DataOutputStream dos = null;
        ProcessBar bar = null;

        try {
            dos = new DataOutputStream(socket.getOutputStream());  // ack
        } catch (IOException e) {
            logger.error("create dos error (can not send ack, file name {} ) >>> ", mess.getFileName(), e);
            return false;
        }

        if (!refuseAllFile) {
            int res = JOptionPane.showConfirmDialog(
                    null,
                    "是否接收来自" + socket.getInetAddress() + "的文件 -->" + mess.getFileName(),
                    "提示",
                    JOptionPane.YES_NO_CANCEL_OPTION);
            if (res == 0) {
                DataInputStream dis = null;  // 文件输入流
                byte[] buf = new byte[8192];
                long tempLength = 0;  // 实际接收文件的长度
                File file = new File(Utils.getSaveFilePath() + File.separator + mess.getFileName());

                DataOutputStream output = null;  // 文件输出流
                // 接收
                try {
                    dis = new DataInputStream(socket.getInputStream());
                    dos.writeUTF("accept");
                    dos.flush();
                    int readSize = 0;
                    output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
                    bar = new ProcessBar(100, "正在接收文件");
                    while (true) {
                        readSize = dis.read(buf);
                        dos.writeUTF("OK");

                        if (readSize == -1) {
                            break;
                        }
                        tempLength += readSize;
                        bar.updateProgressBar(((double) tempLength / mess.getFileLength()) * 100);
                        output.write(buf, 0, readSize);
                    }

                    output.flush();

                    if (tempLength == mess.getFileLength()) {
                        JOptionPane.showMessageDialog(
                                null,
                                "接收来自" + socket.getInetAddress() + "的文件[" + mess.getFileName() + "]成功",
                                "提示",
                                JOptionPane.INFORMATION_MESSAGE);
                        logger.error("File {} Accept Success", mess.getFileName());
                    } else {
                        JOptionPane.showMessageDialog(
                                null,
                                "接收来自" + socket.getInetAddress() + "的文件[" + mess.getFileName() + "]失败",
                                "提示",
                                JOptionPane.INFORMATION_MESSAGE);
                        logger.error("File {} Accept Error", mess.getFileName());
                        file.delete();
                    }
                } catch (Exception e) {
                    JOptionPane.showMessageDialog(
                            null,
                            "接收来自" + socket.getInetAddress() + "的文件[" + mess.getFileName() + "]失败",
                            "提示",
                            JOptionPane.INFORMATION_MESSAGE);
                    logger.error("File {} Accept Error >>> ", mess.getFileName(), e);
                } finally {
                    Utils.closeProcessBar(bar);
                    Utils.closeDataInputStream(dis);
                    Utils.closeDataOutputStream(output);
                    Utils.closeDataOutputStream(dos);
                }
            } else {
                try {
                    dos.writeUTF("refuse accept");
                    logger.info("refuse accept");
                } catch (IOException e) {
                    logger.error("refuse accept Error >>> ", e);
                }
                JOptionPane.showMessageDialog(
                        null,
                        "拒绝接收来自" + socket.getInetAddress() + "的文件-->" + mess.getFileName(),
                        "提示",
                        JOptionPane.INFORMATION_MESSAGE);
            }
            return true;
        } else {
            try {
                dos.writeUTF("refuse accept");
                logger.info("refuse accept (pattern)");
            } catch (IOException e) {
                logger.error("refuse accept Error >>> ", e);
            } finally {
                Utils.closeDataOutputStream(dos);
            }
            return false;
        }
    }

    /**
     * 将来自客户端的文本放入共享池
     *
     * @param mess
     * @param socket
     * @return
     */
    private boolean acceptText(TextMess mess, Socket socket) {
        DataOutputStream dos = null;
        if (!Utils.isEmpty(mess.getText())) {
            lruCache.put(mess.getText());
            try {
                dos = new DataOutputStream(socket.getOutputStream());
                dos.writeUTF("OK");
                dos.flush();
            } catch (IOException e) {
                logger.error("can not send OK(ack) to targetSocket >>> ", e);
            } finally {
                Utils.closeDataOutputStream(dos);
            }
            return true;
        }
        return false;
    }

    private void pushSharedTextToClient(CacheMess mess, Socket socket) {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(socket.getOutputStream());
            mess.setCache(lruCache);
            oos.writeObject(mess);
            oos.flush();
        } catch (Exception e) {
            logger.error("pushSharedTextToClient Error >>> ", e);
        } finally {
            Utils.closeObjectOutputStream(oos);
        }

    }

    /**
     * client拉取server端文件
     *
     * @param mess   标识, 并携带文件列表返回
     * @param socket socket
     * @param ois    上层对象输入流， 上层关闭
     */
    private void pushFileInFilePool(PullFileMess mess, Socket socket, ObjectInputStream ois) {
        ObjectOutputStream oos = null;  // tcp 对象输出
        DataInputStream input = null;  // ack
        DataOutputStream dos = null;  // tcp 文件输出
        DataInputStream dis = null;  // 本地文件输入流
        ProcessBar bar = null;  // 进度条

        try {
            // 获取文件池中的文件
            File filePool = new File(Utils.getFilePoolPath());
            File[] tempList = filePool.listFiles();
            dos = new DataOutputStream(socket.getOutputStream());
            if (tempList == null || tempList.length == 0) {
                dos.writeUTF("NoFiles");
                return;
            } else {
                dos.writeUTF("HaveFiles");
            }
            String[] fileList = new String[tempList.length];
            for (int i = 0; i < tempList.length; ++i) {
                fileList[i] = tempList[i].getName();
            }

            // 发送文件池中的文件名列表
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(fileList);
            oos.flush();

            // 接收文件名
            FileMess fileMess = (FileMess) ois.readObject();

            File targetFile = new File(filePool, fileMess.getFileName());
            oos.writeObject(new FileMess(ContentType.FileType, targetFile.length(), fileMess.getFileName()));
            oos.flush();

            dis = new DataInputStream((new FileInputStream(targetFile)));
            byte[] buf = new byte[8192];
            int readSize = 0;
            long tempLength = 0;
            bar = new ProcessBar(100, "正在发送文件");
            input = new DataInputStream(socket.getInputStream());
            while (true) {
                readSize = dis.read(buf);
                if (readSize == -1) {
                    break;
                }
                tempLength += readSize;
                dos.write(buf, 0, readSize);
                bar.updateProgressBar(((double) tempLength / targetFile.length()) * 100);
                if (!input.readUTF().equals("OK")) {
                    logger.error("pushFileInFilePool Transfer File Error >>> lost connection");
                    break;
                }
            }
            dos.flush();
            logger.info("File {} Transfer Success", targetFile.getName());
        } catch (Exception e) {
            logger.error("pushFileInFilePool Error --> ", e);
        } finally {
            Utils.closeProcessBar(bar);
            Utils.closeDataOutputStream(dos);
            Utils.closeDataInputStream(dis);
            Utils.closeObjectOutputStream(oos);
            Utils.closeDataInputStream(input);
        }
    }

    /**
     * 监听器
     */
    private void listen() {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(Utils.getPort());
            logger.info("New {} to listen <TCP>", serverSocket);
            while (isLoop) {
                try {
                    socket = serverSocket.accept();
                    Socket finalSocket = socket;
                    threadPool.execute(() -> {
                        try {
                            dispatcher(finalSocket);
                        } catch (Exception e) {
                            logger.error("Dispatcher Socket {} Error >>> ", finalSocket, e);
                        } finally {
                            Utils.closeSocket(finalSocket);
                        }
                    });
                } catch (IOException e) {
                    logger.error("Accept Connection Error <socket>", e);
                }
            }
        } catch (IOException e) {
            logger.error("Create Listener Error >>> ", e);
        } finally {
            Utils.closeServerSocket(serverSocket);
        }
    }

    /**
     * 分发器
     *
     * @param socket
     */
    private void dispatcher(Socket socket) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(socket.getInputStream());
            Message message = (Message) ois.readObject();
            if (message.getContentType() == ContentType.TextType) {
                if (!acceptText((TextMess) message, socket)) {
                    logger.error("-->{}<-- can not put into cache, maybe is null or empty", ((TextMess) message).getText());
                }
            } else if (message.getContentType() == ContentType.FileType) {
                acceptFile((FileMess) message, socket);
            } else if (message.getContentType() == ContentType.PullTextType) {
                pushSharedTextToClient((CacheMess) message, socket);
            } else if (message.getContentType() == ContentType.PullFileType) {
                pushFileInFilePool((PullFileMess) message, socket, ois);
            }
        } catch (Exception e) {
            logger.error("Dispatcher ClassNotFoundException Or IOException >>> ", e);
        } finally {
            Utils.closeObjectInputStream(ois);
        }
    }

    @Override
    public void closeListen() {
        setLoop(false);
    }

    @Override
    public void openListen() {
        setLoop(true);
        listen();
    }

    @Override
    public void openRefuseFileMode() {
        setRefuseAllFile(true);
    }

    @Override
    public void closeRefuseFileMode() {
        setRefuseAllFile(false);
    }
}
